package com.thiswhyme.demo.msgpush.core.impl.nio;

import com.thiswhyme.demo.msgpush.core.WsConstant;
import com.thiswhyme.demo.msgpush.core.config.MsgPushProperties;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;

/**
 * @description: netty服务
 * @author: thiswhyme
 * @date: 2021/9/29
 */
@Slf4j
public class NettyServer {

    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;
    private final NioWsHandler nioWsHandler;
    private final MsgPushProperties.NioProperties nioProperties;

    public NettyServer(NioWsHandler nioWsHandler, MsgPushProperties.NioProperties nioProperties) {
        this.nioWsHandler = nioWsHandler;
        this.nioProperties = nioProperties;
    }

    /**
     * 启动
     */
    private void doInit() {
        bossGroup = new NioEventLoopGroup();
        workGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        // bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作
        bootstrap.group(bossGroup, workGroup);
        // 设置NIO类型的channel
        bootstrap.channel(NioServerSocketChannel.class);
        // 设置监听端口
        bootstrap.localAddress(new InetSocketAddress(nioProperties.getPort()));
        // 连接到达时会创建一个通道
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                // 解码器
                ch.pipeline().addLast(new HttpServerCodec());
                ch.pipeline().addLast(new ObjectEncoder());
                // 用于大数据的分区传输
                ch.pipeline().addLast(new ChunkedWriteHandler());
                // 数据分段聚合器
                ch.pipeline().addLast(new HttpObjectAggregator(nioProperties.getMaxContentLength()));
                // 将http协议升级为ws协议，保持长连接
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(nioProperties.getPath(),
                        WsConstant.PROTOCOL_WEBSOCKET, true,
                        nioProperties.getMaxContentLength() * 10));
                // 自定义的业务handler
                ch.pipeline().addLast(nioWsHandler);
                // 客户端心跳检测，若10s内无读事件则触发心跳处理方法HeartBeatHandler#userEventTriggered
                ch.pipeline().addLast(new IdleStateHandler(nioProperties.getHeartbeat() , 0 , 0));
                ch.pipeline().addLast(new HeartBeatHandler());
            }
        });
        try {
            // 配置完成，开始绑定server，通过调用sync同步方法阻塞直到绑定成功
            ChannelFuture channelFuture = bootstrap.bind().sync();
            log.info("Netty服务已启动，监听URL：{}", channelFuture.channel().localAddress());
            // 对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("Netty服务启动失败", e);
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

    /**
     * 释放资源
     * @throws InterruptedException
     */
    @PreDestroy
    public void destroy() throws InterruptedException {
        log.info("开始销毁Netty服务");
        if(bossGroup != null){
            bossGroup.shutdownGracefully().sync();
        }
        if(workGroup != null){
            workGroup.shutdownGracefully().sync();
        }
    }

    @PostConstruct()
    public void init() {
        new Thread(() -> {
            log.info("开始初始化Netty服务");
            doInit();
        }).start();
    }
}
